<?php

// @formatter:off
/**
 * @file WCSessions.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-05-04 01:33:52 +0000 (Sun, 04 May 2014) $
 *
 * $Id: WCSessions.php 111 2014-05-04 01:33:52Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/security/WCSessions.php $
 */
// @formatter:on
//
// If the session management is not yet started, it starts here.
if(!session_id()) {
	session_start();
}

/**
 * @class WCSessions This class holds the logic to represent and manage a
 * session. Thanks to it, users are allowed to log into wcomix and keep their
 * sessions stuff for some time.
 */
class WCSessions extends WCSingleton {
	/**
	 * @var WCConnection Database pointer shortcut.
	 */
	protected $_db = null;
	/**
	 * @var string Database table's prefix shortcut.
	 */
	protected $_dbprefix = "";
	/**
	 * @var string Current PHP session id.
	 */
	protected $_id = "";
	/**
	 * @var string This is the IP from where a user is using this session.
	 */
	protected $_ip = "";
	/**
	 * @var boolean This flag indicates if the current user is logged in or
	 * not.
	 */
	protected $_isLogged = false;
	/**
	 * @var WCUser Pointer to the current user.
	 */
	protected $_user = null;
	/**
	 * @var int Pointer to the current user ID.
	 */
	protected $_userId = false;
	/**
	 * @var WCSession This object holds and manages all session stuff.
	 */
	protected $_session = null;
	/**
	 * @var WCSessionKeep This object holds and manages session data based on
	 * cookies.
	 */
	protected $_sessionKeep = null;
	/**
	 * Class constructor. Loads all shortcuts and all required stuff.
	 */
	protected function __construct() {
		//
		// Loading global pointers.
		global $wcDatabaseAccess;
		//
		// Loading session ID shortcut.
		$this->_id = session_id();
		//
		// Cleaning session basic variables.
		$this->clearVariables();
		//
		// Saving current user IP to make the session a little bit more
		// secure.
		$this->_ip = isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : "unknown";
		//
		// Loading database shortcuts.
		// @{
		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $wcDatabaseAccess["prefix"];
		// @}
		//
		// Creating an object to manage the real session.
		$this->_session = new WCSession();
		//
		// Creating an object to manage flags and data that allows a user
		// to keep a session even after closing a browser.
		$this->_sessionKeep = new WCSessionKeep();
		//
		// Clearing old sessions.
		$this->clearSessions();
		//
		// Checking and loading previous session and user.
		$this->checkSession();
	}
	//
	// Public methods.
	/**
	 * This method allows to get current session ID.
	 *
	 * @return string Returns a session ID.
	 */
	public function id() {
		return $this->_id;
	}
	/**
	 * This method allows to get the IP address from where this session is
	 * being called.
	 *
	 * @return string Returns an IP address.
	 */
	public function ip() {
		return $this->_ip;
	}
	/**
	 * This method allows to know whether a user is logged-in or not.
	 *
	 * @return boolean Returns current user log-in status.
	 */
	public function isLogged() {
		return $this->_isLogged;
	}
	/**
	 * This method tries to log in the current session into database.
	 *
	 * @param int $id Identifier of user to be logged in.
	 * @return boolean Returns true when the user was successfully logged in.
	 */
	public function login($id) {
		//
		// Checking if a user isn't currently logged-in.
		if(!$this->isLogged()) {
			//
			// Creating a statement to generate a new session inside
			// the database.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "insert\n";
				$query .= "        into {$this->_dbprefix}sessions (sess_id, sess_user_id, sess_ip)\n";
				$query .= "        values (:session, :user, :ip)";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":session" => $this->_id,
				":user" => $id,
				":ip" => $this->_ip
			);
			//
			// Creating the new session.
			$stmt->execute($params);
			//
			// Checking and loading new session and user.
			return $this->checkSession();
		} else {
			//
			// At this point, a user is currently logged-in. No
			// further actions are taken.
			return true;
		}
	}
	/**
	 * Removes current session id from database. In other words, the user
	 * logs-out.
	 *
	 * @return Returns true when session was successfully removed.
	 */
	public function logout() {
		//
		// Creating a statement to remove sessions from database.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[sessions]";
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from     {$this->_dbprefix}sessions\n";
			$query .= "where           sess_id = :session";

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		//
		// Building database parameters.
		$params = array(":session" => $this->_id);
		//
		// Removing session.
		$stmt->execute($params);
		//
		// Creating a statement to remove database session keepers
		// (cookie based sessions).
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[keeps]";
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from     {$this->_dbprefix}session_keeps\n";
			$query .= "where           skp_user = :user\n";
			$query .= " and            skp_code = :code\n";
			$query .= " and            skp_salt = :salt\n";
			$query .= " and            skp_ip   = :ip\n";

			$stmt = $this->_db->prepare($stmtId, $query);
		}
		// @}
		//
		// Building database parameters.
		$params = array(
			":user" => $this->userId(),
			":code" => WCSessionKeep::CodeGenertor($this->_session->salt),
			":salt" => $this->_session->salt,
			":ip" => $this->_ip
		);
		//
		// Removing session keeper.
		$stmt->execute($params);
		//
		// Checking and loading session. If everything is okey, this
		// should return false.
		return !$this->checkSession();
	}
	/**
	 * This method allows to get the current page where the user is.
	 *
	 * @return string Returns a page URI.
	 */
	public function page() {
		return isset($_SERVER["REQUEST_URI"]) ? $_SERVER["REQUEST_URI"] : "unknown";
	}
	/**
	 * This method generates a session keeper on the database. Such session
	 * keeper will help this class to load user even after they close their
	 * browser.
	 */
	public function setRememberMe() {
		//
		// A keeper can only be generated for a logged-in user.
		if($this->isLogged()) {
			//
			// Getting current salt. This is a random hash code that
			// depends on each user.
			$salt = $this->_session->salt;
			//
			// Generatian a session code for the keeper based on the
			// salt.
			$code = WCSessionKeep::CodeGenertor($salt);
			//
			// Creating a statement to add a new keeper.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "insert\n";
				$query .= "        into {$this->_dbprefix}session_keeps (skp_user, skp_code, skp_salt, skp_ip, skp_timestamp)\n";
				$query .= "        values (:user, :code, :salt, :ip, now())";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":user" => $this->userId(),
				":code" => $code,
				":salt" => $salt,
				":ip" => $this->_ip
			);
			//
			// Adding a new keeper.
			if($stmt->execute($params)) {
				//
				// Loading internal keeper manager with the new
				// ID.
				$this->_sessionKeep->load($this->_db->lastInsertId());
				//
				// Updating browser's cookies.
				$this->_sessionKeep->updateCookies();
			}
		}
	}
	/**
	 * This method allows to access current user pointer.
	 *
	 * @return WCUser Returns a user pointer.
	 */
	public function user() {
		return $this->_user;
	}
	/**
	 * This method allows to access current user id.
	 *
	 * @return int Returns a user id.
	 */
	public function userId() {
		return $this->_userId;
	}
	//
	// Protected methods.
	/**
	 * This method checks if current session id is registered in database.
	 * When it is already registered, it updates that entry, mainly to keep
	 * session aging field up to date.
	 *
	 * @return boolean Return true when it is already registered. In other
	 * words, it sets '$this->_isLogged'.
	 */
	protected function checkSession() {
		//
		// Setting current session as not logged-in.
		$this->_isLogged = false;
		//
		// Attempting to load session from database base on current
		// session id.
		@$this->_session->load($this->_id);
		//
		// Checking if session loads without problems and the IP is the
		// right one.
		if($this->_session->ok() && $this->_session->ip == $this->_ip) {
			//
			// Loading global pointers.
			global $wcUsersHolder;
			//
			// Trying to load user from current session information.
			$user = $wcUsersHolder->item($this->_session->user_id);
			//
			// Checking that the user exists and has no issues.
			if($user && $user->ok()) {
				//
				// At this point, session is considered to be
				// logged-in.
				$this->_isLogged = true;
				//
				// Saving current user ID shortcut.
				$this->_userId = $user->id();
				//
				// Saving current user shortcut.
				$this->_user = $user;
			}
			//
			// Saving current page for some user tracking.
			$this->_session->page = $this->page();
		} elseif(!$this->_session->ok()) {
			//
			// At this point, session was not correctly loaded.
			//
			// Creating a first attempt marker.
			static $attempt = true;
			//
			// Trying to load session using some browser cookies.
			if($attempt && $this->_sessionKeep->loadByCookies()) {
				//
				// At this point, it's possible to load a user
				// using browser's cookies.
				//
				// Next time won't be the first attempt.
				$attempt = false;
				//
				// Forcing a user to be logged-in.
				$this->login($this->_sessionKeep->user);
				//
				// Updating browser's session cookies.
				$this->_sessionKeep->updateCookies();
				//
				// Updating current salt marker.
				$this->_session->salt = $this->_sessionKeep->salt;
			}
			//
			// Resetting marker.
			$attempt = true;
		}
		//
		// If the user is still not logged-in, the information from
		// browser's cookie can be of some help.
		if(!$this->isLogged()) {
			//
			// Getting information about the last user that logged
			// from session based on cookies.
			$user = $this->_sessionKeep->lastUser();
			//
			// Checking that the user exists and it has no issues.
			if($user && $user->ok()) {
				//
				// Loading global pointers.
				global $wcThemeAssigns;
				//
				// Adding variables to be used by themes.
				// @{
				$wcThemeAssigns["LAST-USER-ID"] = $user->id();
				$wcThemeAssigns["LAST-USER-USERNAME"] = $user->username();
				// @}
			}
		}
		//
		// Returning whether a user is logged-in or not.
		return $this->isLogged();
	}
	/**
	 * This method removes expired sessions.
	 */
	protected function clearSessions() {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Creating a statement to remove old sessions.
		// @{
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from {$this->_dbprefix}sessions\n";
			$query .= "where       sess_timestamp < (now() - interval :maxtime second)";
			$stmt = $this->_db->prepare($stmtId, $query, true);
		}
		// @}
		//
		// Running cleaning statement.
		$stmt->execute(array(":maxtime" => $wcDefaults["session-max-time"]));
	}
	/**
	 * This method clears every instance property that might indicate it is
	 * logged in.
	 */
	protected function clearVariables() {
		//
		// Setting session as not logged.
		$this->_isLogged = false;
		//
		// Clearing current user id shortcut.
		$this->_userId = false;
		//
		// Clearing current user shortcut.
		$this->_user = null;
	}
}
